var _ = require('../util')
var Observer = require('../observer')
var Dep = require('../observer/dep')

/**
 * Setup the scope of an instance, which contains:
 * - observed data
 * - computed properties
 * - user methods
 * - meta properties
 */

exports._initScope = function () {
  this._initData()
  this._initComputed()
  this._initMethods()
  // this._initMeta()
}

/**
 * Initialize the data. 
 */

const KEY_WORDS = ['$index', '$value', '$event']
exports._initData = function () {
  // proxy data on instance
  var data = this._data
  var i, key
  // // make sure all props properties are observed
  // var props = this.$options.props
  // if (props) {
  //   i = props.length
  //   while (i--) {
  //     key = _.camelize(props[i])
  //     if (!(key in data)) {
  //       data[key] = null
  //     }
  //   }
  // }
  var keys = Object.keys(data)
  i = keys.length
  while (i--) {
    key = keys[i]
    if (KEY_WORDS.indexOf(key) > -1 || !_.isReserved(key)) {
      this._proxy(key)
    }
  }
  // observe data
  Observer.create(data).addVm(this)
}

// /**
//  * Swap the isntance's $data. Called in $data's setter.
//  *
//  * @param {Object} newData
//  */

// exports._setData = function (newData) {
//   newData = newData || {}
//   var oldData = this._data
//   this._data = newData
//   var keys, key, i
//   // unproxy keys not present in new data
//   keys = Object.keys(oldData)
//   i = keys.length
//   while (i--) {
//     key = keys[i]
//     if (!_.isReserved(key) && !(key in newData)) {
//       this._unproxy(key)
//     }
//   }
//   // proxy keys not already proxied,
//   // and trigger change for changed values
//   keys = Object.keys(newData)
//   i = keys.length
//   while (i--) {
//     key = keys[i]
//     if (!this.hasOwnProperty(key) && !_.isReserved(key)) {
//       // new property
//       this._proxy(key)
//     }
//   }
//   oldData.__ob__.removeVm(this)
//   Observer.create(newData).addVm(this)
//   this._digest()
// }

/**
 * Proxy a property, so that
 * vm.prop === vm._data.prop
 *
 * @param {String} key
 */

exports._proxy = function (key) {
  // need to store ref to self here
  // because these getter/setters might
  // be called by child instances!
  var self = this
  Object.defineProperty(self, key, {
    configurable: true,
    enumerable: true,
    get: function proxyGetter () {
      return self._data[key]
    },
    set: function proxySetter (val) {
      self._data[key] = val
    }
  })
}

/**
 * Unproxy a property.
 *
 * @param {String} key
 */

exports._unproxy = function (key) {
  delete this[key]
}

// /**
//  * Force update on every watcher in scope.
//  */

// exports._digest = function () {
//   var i = this._watchers.length
//   while (i--) {
//     this._watchers[i].update()
//   }
//   var children = this._children
//   i = children.length
//   while (i--) {
//     var child = children[i]
//     if (child.$options.inherit) {
//       child._digest()
//     }
//   }
// }

/**
 * Setup computed properties. They are essentially
 * special getter/setters
 */

function noop () {}
exports._initComputed = function () {
  // var computed = this.$options.computed
  var computed = this._computed
  if (computed) {
    for (var key in computed) {
      var userDef = computed[key]
      var def = {
        enumerable: true,
        configurable: true
      }
      if (typeof userDef === 'function') {
        def.get = _.bind(userDef, this)
        def.set = noop
      } else {
        def.get = userDef.get
          ? _.bind(userDef.get, this)
          : noop
        def.set = userDef.set
          ? _.bind(userDef.set, this)
          : noop
      }
      Object.defineProperty(this, key, def)
    }
  }
}

/**
 * Setup instance methods. Methods must be bound to the
 * instance since they might be called by children
 * inheriting them.
 */

exports._initMethods = function () {
  // var methods = this.$options.methods
  var methods = this._methods
  if (methods) {
    for (var key in methods) {
      this[key] = _.bind(methods[key], this)
    }
  }
}

// /**
//  * Initialize meta information like $index, $key & $value.
//  */

// exports._initMeta = function () {
//   var metas = this.$options._meta
//   if (metas) {
//     for (var key in metas) {
//       this._defineMeta(key, metas[key])
//     }
//   }
// }

// /**
//  * Define a meta property, e.g $index, $key, $value
//  * which only exists on the vm instance but not in $data.
//  *
//  * @param {String} key
//  * @param {*} value
//  */

// exports._defineMeta = function (key, value) {
//   var dep = new Dep()
//   Object.defineProperty(this, key, {
//     enumerable: true,
//     configurable: true,
//     get: function metaGetter () {
//       if (Observer.target) {
//         Observer.target.addDep(dep)
//       }
//       return value
//     },
//     set: function metaSetter (val) {
//       if (val !== value) {
//         value = val
//         dep.notify()
//       }
//     }
//   })
// }
